﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;

namespace NQueueing
{
    /// <summary>
    /// Базовый класс для СМО.
    /// </summary>
    /// <typeparam name="T">Тип элемента очереди.</typeparam>
    public abstract class ServerQueueBase<T> : IServerQueue<T>
    {
        protected int _enqueueTimeout = 50;
        /// <summary>
        /// Среднее время ожиданя между моментами помещения в очередь заявки.
        /// </summary>
        public int EnqueueTimeout
        {
            get { return _enqueueTimeout; }
            set { _enqueueTimeout = value; }
        }

        protected int _dequeueTimeout = 100;
        /// <summary>
        /// Среднее время ожидания между моментами извлечения заявок из очереди.
        /// </summary>
        public int DequeueTimeout
        {
            get { return _dequeueTimeout; }
            set { _dequeueTimeout = value; }
        }

        protected int _maxThreadsCount;
        /// <summary>
        /// Максимальное число потоков обработки.
        /// </summary>
        public int MaxThreadsCount
        {
            get { return _maxThreadsCount; }
        }

        protected int _maxQueueSize;
        /// <summary>
        /// Максимальный размер очереди.
        /// </summary>
        public int MaxQueueSize
        {
            get { return _maxQueueSize; }
        }

        protected volatile bool _shouldStop = true;
        protected Thread[] _workers = null;

        protected object _sync = new object();

        protected object _syncQueue = new object();
        protected Queue<T> _queue = null;

        /// <summary>
        /// Конструктор.
        /// </summary>
        /// <param name="maxQueueSize">Максимальный размер очереди.</param>
        /// <param name="maxThreadsCount">Максимальное число потоков обработки.</param>
        protected ServerQueueBase(int maxQueueSize, int maxThreadsCount)
        {
            if (maxQueueSize > 0)
            {
                _queue = new Queue<T>(maxQueueSize);
                _maxQueueSize = maxQueueSize;
            }
            else
            {
                throw new ArgumentOutOfRangeException("maxQueueSize");
            }

            if (maxThreadsCount > 0)
            {
                _workers = new Thread[maxThreadsCount];
                for (int i = 0; i < maxThreadsCount; i++)
                {
                    _workers[i] = new Thread(Workflow);
                    _workers[i].Name = i.ToString();
                }
                _maxThreadsCount = maxThreadsCount;
            }
            else
            {
                throw new ArgumentOutOfRangeException("maxThreadsCount");
            }
        }

        /// <summary>
        /// Запускает рабочие потоки.
        /// </summary>
        public virtual void Start()
        {
            lock (_sync)
            {
                _shouldStop = false;
                ForeachWorkers(thread => thread.Start());
            }
        }

        /// <summary>
        /// Останавливает прием заявок в очередь, и завершает обработку.
        /// </summary>
        public virtual void Close()
        {
            lock (_sync)
            {
                _shouldStop = true; // В очередь больше не вставать
                ForeachWorkers(thread => thread.Join()); // Дождемся пока оператор обслужит остаток очереди.                
            }
        }

        /// <summary>
        /// Прерывает обработку очередей.
        /// </summary>
        public virtual void Abort()
        {
            lock (_sync)
            {
                _shouldStop = true; // В очередь больше не вставать
                ForeachWorkers(thread => thread.Abort());
            }
        }


        protected void ForeachWorkers(Action<Thread> action)
        {
            for (int i = 0; i < _workers.Length; i++)
            {
                action(_workers[i]);
            }
        }

        /// <summary>
        /// Пытается поместить объект <paramref name="item"/> в очередь обработки.
        /// </summary>
        /// <param name="item">Элемент обработки.</param>
        /// <returns>Результат помещения в очередь.</returns>
        public virtual EnqueueStatus TryEnqueue(T item)
        {
            return ThreadSafeEnqueue
                (
                queue => queue.Count < _maxQueueSize,
                queue => queue.Enqueue(item)
                );

            //lock (_sync) // Вставать в очередь по очереди, никаких гонок
            //{
            //    if (_shouldStop) // volatile-тип
            //    {
            //        return EnqueueStatus.QueueClosed; // Заявки больше не принимаем.
            //    }
            //    else
            //    {
            //        lock (_syncQueue)
            //        {
            //            if (_queue.Count < _maxQueueSize)
            //            {
            //                _queue.Enqueue(item);
            //                return EnqueueStatus.Ok;
            //            }
            //            else
            //            {
            //                return EnqueueStatus.QueueFull; // Очередь переполнена
            //            }
            //        }
            //    }
            //}
        }

        /// <summary>
        /// Помещает объект <paramref name="item"/> в очередь обработки. 
        /// При помещении объекта в очередь, в случае если очередь полна, ждет и снова пытается положить.
        /// </summary>
        /// <param name="item">Элемент обработки.</param>
        public virtual void Enqueue(T item)
        {
            Enqueue(item, _enqueueTimeout);
        }

        /// <summary>
        /// Помещает объект <paramref name="item"/> в очередь обработки. 
        /// При помещении объекта в очередь, в случае если очередь полна, ждет и снова пытается положить.
        /// </summary>
        /// <param name="item">Элемент обработки.</param>
        /// <param name="timeout">Время ожидания между попытками.</param>
        public virtual void Enqueue(T item, int timeout)
        {
            while (!ParseEnqueueStatus(TryEnqueue(item)))
            {
                Thread.Sleep(timeout);
            }
        }


        /// <summary>
        /// Пытается поместить массив объектов <paramref name="items"/> в очередь обработки.
        /// </summary>
        /// <param name="items">Массив элементов обработки.</param>
        /// <returns>Результат помещения в очередь.</returns>
        public virtual EnqueueStatus TryEnqueue(T[] items)
        {
            int count = items.Length;
            return ThreadSafeEnqueue
                (
                queue => queue.Count + count - 1 < _maxQueueSize,
                queue =>
                {
                    for (int i = 0; i < count; i++)
                    {
                        queue.Enqueue(items[i]);
                    }
                }
                );

            //lock (_sync) // Вставать в очередь по очереди, никаких гонок
            //{
            //    if (_shouldStop) // volatile-тип
            //    {
            //        return EnqueueStatus.QueueClosed; // Заявки больше не принимаем.
            //    }
            //    else
            //    {
            //        int count = items.Length;
            //        lock (_syncQueue)
            //        {
            //            if (_queue.Count + count - 1 < _maxQueueSize)
            //            {
            //                for (int i = 0; i < count; i++)
            //                {
            //                    _queue.Enqueue(items[i]);
            //                }
            //                return EnqueueStatus.Ok;
            //            }
            //            else
            //            {
            //                return EnqueueStatus.QueueFull; // Очередь переполнена
            //            }
            //        }
            //    }
            //}
        }

        /// <summary>
        /// Помещает массив объектов <paramref name="items"/> в очередь обработки. 
        /// При помещении массива объектов в очередь, в случае если очередь полна, ждет и снова пытается положить массив целиком.
        /// </summary>
        /// <param name="items">Массив элементов обработки.</param>
        public virtual void Enqueue(T[] items)
        {
            Enqueue(items, _enqueueTimeout);
        }

        /// <summary>
        /// Помещает массив объектов <paramref name="items"/> в очередь обработки. 
        /// При помещении массива объектов в очередь, в случае если очередь полна, ждет и снова пытается положить массив целиком.
        /// </summary>
        /// <param name="items">Массив элементов обработки.</param>
        /// <param name="timeout">Время ожидания между попытками.</param>
        public virtual void Enqueue(T[] items, int timeout)
        {
            while (!ParseEnqueueStatus(TryEnqueue(items)))
            {
                Thread.Sleep(timeout);
            }
        }

        private bool ParseEnqueueStatus(EnqueueStatus status)
        {
            switch (status)
            {
                case EnqueueStatus.Ok:
                    {
                        return true;
                    }
                case EnqueueStatus.QueueFull:
                    {
                        return false;
                    }
                case EnqueueStatus.QueueClosed:
                    {
                        throw new InvalidOperationException("EnqueueStatus.QueueClosed");
                    }
                default:
                    {
                        throw new NotSupportedException(status.ToString());
                    }
            }
        }

        private EnqueueStatus ThreadSafeEnqueue(Predicate<Queue<T>> checkSize, Action<Queue<T>> enqueue)
        {
            lock (_sync) // Вставать в очередь по очереди, никаких гонок
            {
                if (_shouldStop) // volatile-тип
                {
                    return EnqueueStatus.QueueClosed; // Заявки больше не принимаем.
                }
                else
                {
                    lock (_syncQueue)
                    {
                        if (checkSize(_queue))
                        {
                            enqueue(_queue);
                            return EnqueueStatus.Ok;
                        }
                        else
                        {
                            return EnqueueStatus.QueueFull;
                        }
                    }
                }
            }
        }

        protected abstract void Workflow();
    }
}
